﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Globalization;
	using System.Text;
	using System.Web;

	/// <summary>
	/// Defines a list of named literal JavaScript values to be generated into jQuery option initializers.
	/// </summary>
	public class PropertyCollection : IEnumerable<string> {


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the GreenIsomer.Web.Controls.JQuery.Properties class.
		/// </summary>
		public PropertyCollection() {
			Items = new Dictionary<string, string>();
		}

		#endregion


		#region Properties

		/// <summary>
		/// Gets or sets the raw dictionary of named JavaScript literals added.
		/// </summary>
		protected Dictionary<string, string> Items { get; private set; }

		/// <summary>
		/// Gets the number named option values added.
		/// </summary>
		public virtual int Count {
			get { return Items.Count; }
		}

		#endregion


		#region Methods: Value

		/// <summary>
		/// Defines a literal JavaScript boolean value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A boolean value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, bool value) {
			AddScript(name, value ? "true" : "false");
		}

		/// <summary>
		/// Defines a literal JavaScript boolean array to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A boolean array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, bool[] value) {
			if(value == null) {
				AddNullValue(name);
				return;
			}

			StringBuilder builder = new StringBuilder();
			builder.Append("[");
			for(int i = 0; i < value.Length; i++) {
				if(i != 0) builder.Append(",");
				builder.Append(value[i] ? "true" : "false");
			}
			builder.Append("]");
			AddScript(name, builder.ToString());
		}

		/// <summary>
		/// Defines a literal JavaScript string value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A character value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, char value) {
			AddValue(name, value.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		/// Defines a literal JavaScript string value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="buffer">A character array value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, char[] buffer) {
			AddValue(name, buffer == null ? null : new string(buffer));
		}

		/// <summary>
		/// Defines a literal JavaScript string value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="buffer">A character array value.</param>
		/// <param name="index">The first character to append from the array.</param>
		/// <param name="count">The number of characters to append.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, char[] buffer, int index, int count) {
			AddValue(name, buffer == null ? null : new string(buffer, index, count));
		}

		/// <summary>
		/// Defines a literal JavaScript date/time value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A date/time value.</param>
		/// <param name="includeTime">Whether or not to include the time portion.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, DateTime value, bool includeTime) {
			AddScript(name, ScriptBuilder.GetCreateDateTimeScript(value, includeTime));
		}

		/// <summary>
		/// Defines a literal JavaScript date/time array to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A date/time array.</param>
		/// <param name="includeTime">Whether or not to include the time portion.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, DateTime[] value, bool includeTime) {
			if(value == null) {
				AddNullValue(name);
				return;
			}

			StringBuilder builder = new StringBuilder();
			builder.Append("[");
			for(int i = 0; i < value.Length; i++) {
				if(i != 0) builder.Append(",");
				builder.Append(ScriptBuilder.GetCreateDateTimeScript(value[i], includeTime));
			}
			builder.Append("]");
			AddScript(name, builder.ToString());
		}

		/// <summary>
		/// Defines a literal JavaScript numeric value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, decimal value) {
			AddScript(name, value.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		/// Defines a literal JavaScript numeric array to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, decimal[] value) {
			if(value == null) AddNullValue(name);
			else AddScript(name, "[" + string.Join(",", Array.ConvertAll(value, new Converter<decimal, string>(Convert.ToString))) + "]");
		}

		/// <summary>
		/// Defines a literal JavaScript numeric value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, double value) {
			AddScript(name, value.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		/// Defines a literal JavaScript numeric array to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, double[] value) {
			if(value == null) AddNullValue(name);
			else AddScript(name, "[" + string.Join(",", Array.ConvertAll(value, new Converter<double, string>(Convert.ToString))) + "]");
		}

		/// <summary>
		/// Defines a literal JavaScript numeric value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, float value) {
			AddScript(name, value.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		/// Defines a literal JavaScript numeric array to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, float[] value) {
			if(value == null) AddNullValue(name);
			else AddScript(name, "[" + string.Join(",", Array.ConvertAll(value, new Converter<float, string>(Convert.ToString))) + "]");
		}

		/// <summary>
		/// Defines a literal JavaScript numeric value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, int value) {
			AddScript(name, value.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		/// Defines a literal JavaScript numeric array to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, int[] value) {
			if(value == null) AddNullValue(name);
			else AddScript(name, "[" + string.Join(",", Array.ConvertAll(value, new Converter<int, string>(Convert.ToString))) + "]");
		}

		/// <summary>
		/// Defines a literal JavaScript numeric value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, long value) {
			AddScript(name, value.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		/// Defines a literal JavaScript numeric array to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, long[] value) {
			if(value == null) AddNullValue(name);
			else AddScript(name, "[" + string.Join(",", Array.ConvertAll(value, new Converter<long, string>(Convert.ToString))) + "]");
		}

		/// <summary>
		/// Defines a literal JavaScript string value to be provided to the jQuery client framework upon initialization using an object's default text output.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">An object providing a literal text value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, object value) {
			if(value == null) AddNullValue(name);
			else AddScript(name, value.ToString());
		}

		/// <summary>
		/// Defines a literal JavaScript string array to be provided to the jQuery client framework upon initialization using each object's default text output.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">An array of objects providing literal text values.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, object[] value) {
			if(value == null) {
				AddNullValue(name);
				return;
			}

			StringBuilder builder = new StringBuilder();
			builder.Append("[");
			for(int i = 0; i < value.Length; i++) {
				if(i != 0) builder.Append(",");
				if(value[i] == null) builder.Append("null");
				else builder.Append("'" + ScriptBuilder.JavaScriptStringEncode(value[i].ToString()) + "'");
			}
			builder.Append("]");
			AddScript(name, builder.ToString());
		}

		/// <summary>
		/// Defines a literal JavaScript string value to be provided to the jQuery client framework upon initialization by replacing the format item in a specified System.String with the text equivalent of the value of a corresponding object instance in a specified array.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="provider">A System.IFormatProvider that supplies culture-specific formatting information.</param>
		/// <param name="format">A composite format string.</param>
		/// <param name="args">An object array containing zero or more objects to format.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public void AddValue(string name, IFormatProvider provider, string format, params object[] args) {
			AddValue(name, provider, false, format, args);
		}

		/// <summary>
		/// Defines a literal JavaScript string value to be provided to the jQuery client framework upon initialization by replacing the format item in a specified System.String with the text equivalent of the value of a corresponding object instance in a specified array.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="provider">A System.IFormatProvider that supplies culture-specific formatting information.</param>
		/// <param name="htmlEncode">Whether or not to encode the final value as HTML.</param>
		/// <param name="format">A composite format string.</param>
		/// <param name="args">An object array containing zero or more objects to format.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, IFormatProvider provider, bool htmlEncode, string format, params object[] args) {
			AddValue(name, string.Format(provider, format, args), htmlEncode);
		}

		/// <summary>
		/// Defines a literal JavaScript string value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A string value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public void AddValue(string name, string value) {
			AddValue(name, value, false);
		}

		/// <summary>
		/// Defines a literal JavaScript string value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A string value.</param>
		/// <param name="htmlEncode">Encode the string value as HTML.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, string value, bool htmlEncode) {
			if(value == null) {
				AddNullValue(name);
				return;
			}
			if(htmlEncode) value = HttpUtility.HtmlEncode(value);
			AddScript(name, "'" + ScriptBuilder.JavaScriptStringEncode(value) + "'");
		}

		/// <summary>
		/// Defines a literal JavaScript numeric value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		[CLSCompliant(false)]
		public virtual void AddValue(string name, uint value) {
			AddScript(name, value.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		/// Defines a literal JavaScript numeric array to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		[CLSCompliant(false)]
		public virtual void AddValue(string name, uint[] value) {
			if(value == null) AddNullValue(name);
			else AddScript(name, "[" + string.Join(",", Array.ConvertAll(value, new Converter<uint, string>(Convert.ToString))) + "]");
		}

		/// <summary>
		/// Defines a literal JavaScript numeric value to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric value.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		[CLSCompliant(false)]
		public virtual void AddValue(string name, ulong value) {
			AddScript(name, value.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		/// Defines a literal JavaScript numeric array to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">A numeric array.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		[CLSCompliant(false)]
		public virtual void AddValue(string name, ulong[] value) {
			if(value == null) AddNullValue(name);
			else AddScript(name, "[" + string.Join(",", Array.ConvertAll(value, new Converter<ulong, string>(Convert.ToString))) + "]");
		}



		/// <summary>
		/// Defines a literal JavaScript value defined by a complex type to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public void AddNullValue(string name) {
			AddScript(name, "null");
		}

		/// <summary>
		/// Defines a literal JavaScript complex type using nested values to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="nestedProperties">An additional properties object to be nested within the given option.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddValue(string name, PropertyCollection nestedProperties) {
			if(nestedProperties == null) {
				AddNullValue(name);
				return;
			}
			if(nestedProperties.Items.Count == 0) {
				AddScript(name, "{}");
				return;
			}

			ScriptBuilder script = new ScriptBuilder();
			script.RenderProperties(nestedProperties);
			AddScript(name, script.ToString());
		}

		/// <summary>
		/// Defines a literal JavaScript null to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">The complex value to be converted into a literal.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public void AddValue(string name, IComplexProperty value) {
			if(name == null) throw new ArgumentNullException("name");
			if(name.Length == 0) throw new ArgumentException("Value is required.", "name");

			if(value == null) AddNullValue(name);
			else value.AddPropertyValue(this, name);
		}

		/// <summary>
		/// Defines a literal JavaScript string value to be provided to the jQuery client framework upon initialization by converting a Pascal-case server enumeration value to a camel case literal.
		/// </summary>
		/// <typeparam name="T">The enumeration type to convert.</typeparam>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">The value to convert.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddEnumValue<T>(string name, T value) where T : struct {
			AddValue(name, ScriptBuilder.GetCamelCaseEnumValue(value));
		}

		/// <summary>
		/// Defines a literal JavaScript string array to be provided to the jQuery client framework upon initialization by converting an array of Pascal-case server enumeration values to camel case literals.
		/// </summary>
		/// <typeparam name="T">The enumeration type to convert.</typeparam>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">The values to convert.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddEnumValue<T>(string name, T[] value) where T : struct {
			StringBuilder builder = new StringBuilder();
			builder.Append("[");
			for(int i = 0; i < value.Length; i++) {
				if(i != 0) builder.Append(",");
				string text = ScriptBuilder.GetCamelCaseEnumValue(value[i]);
				if(text == null) builder.Append("null");
				else builder.Append("'" + ScriptBuilder.JavaScriptStringEncode(text) + "'");
			}
			builder.Append("]");
			AddScript(name, builder.ToString());
		}

		/// <summary>
		/// Adds literal JavaScript to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="script">JavaScript code to add to the option.</param>
		/// <exception cref="ArgumentNullException">The name value cannot be null.</exception>
		/// <exception cref="ArgumentException">The name value cannot be an empty string.</exception>
		public virtual void AddScript(string name, ScriptBuilder script) {
			if(script == null) throw new ArgumentNullException("script");

			AddScript(name, script.ToString());
		}

		/// <summary>
		/// Adds literal JavaScript to be provided to the jQuery client framework upon initialization.
		/// </summary>
		/// <param name="name">The option name of the value to be provided to the jQuery client framework upon initialization.</param>
		/// <param name="value">JavaScript code to add to the option.</param>
		/// <exception cref="ArgumentNullException">Neither value can be null.</exception>
		/// <exception cref="ArgumentException">Neither value can be an empty string.</exception>
		protected virtual void AddScript(string name, string value) {
			if(name == null) throw new ArgumentNullException("name");
			if(name.Length == 0) throw new ArgumentException("Value is required.", "name");
			if(value == null) throw new ArgumentNullException("value");
			if(value.Length == 0) throw new ArgumentException("Value is required.", "value");

			Items[name] = value;
		}

		#endregion


		#region Methods: Emumerator

		/// <summary>
		/// Returns an enumerator that iterates through the added property values.
		/// </summary>
		/// <returns>A Key Value Pair of all JavaScript snippets added.</returns>
		public IEnumerator<string> GetEnumerator() {
			foreach(KeyValuePair<string, string> item in Items)
				yield return item.Key + ":" + item.Value;
		}

		#endregion


		#region Explicit Implentation: IEnumerable

		IEnumerator IEnumerable.GetEnumerator() {
			return GetEnumerator();
		}

		#endregion


	}

}